#include "types.h"
#include "globals.h"
#include "kernel.h"

#include "util/gdb.h"
#include "util/init.h"
#include "util/debug.h"
#include "util/string.h"
#include "util/printf.h"

#include "mm/mm.h"
#include "mm/page.h"
#include "mm/pagetable.h"
#include "mm/pframe.h"
#include "mm/mman.h"

#include "vm/vmmap.h"
#include "vm/shadow.h"
#include "vm/anon.h"

#include "main/acpi.h"
#include "main/apic.h"
#include "main/interrupt.h"
#include "main/cpuid.h"
#include "main/gdt.h"

#include "proc/sched.h"
#include "proc/proc.h"
#include "proc/kthread.h"

#include "drivers/dev.h"
#include "drivers/blockdev.h"
#include "drivers/tty/virtterm.h"

#include "api/exec.h"
#include "api/syscall.h"

#include "fs/vfs.h"
#include "fs/vnode.h"
#include "fs/vfs_syscall.h"
#include "fs/fcntl.h"
#include "fs/stat.h"

#include "test/kshell/kshell.h"

#include "test/usertest.h"

/*extern 
int vfstest_main(kshell_t* ksh, int argc, char **argv);
*/
GDB_DEFINE_HOOK(boot)
GDB_DEFINE_HOOK(initialized)
GDB_DEFINE_HOOK(shutdown)

static void      *bootstrap(int arg1, void *arg2);
static void      *idleproc_run(int arg1, void *arg2);
static kthread_t *initproc_create(void);
static void      *initproc_run(int arg1, void *arg2);
static void       hard_shutdown(void);

static context_t bootstrap_context;

/**
 * This is the first real C function ever called. It performs a lot of
 * hardware-specific initialization, then creates a pseudo-context to
 * execute the bootstrap function in.
 */
void
kmain()
{
        GDB_CALL_HOOK(boot);

        dbg_init();
        dbgq(DBG_CORE, "Kernel binary:\n");
        dbgq(DBG_CORE, "  text: 0x%p-0x%p\n", &kernel_start_text, &kernel_end_text);
        dbgq(DBG_CORE, "  data: 0x%p-0x%p\n", &kernel_start_data, &kernel_end_data);
        dbgq(DBG_CORE, "  bss:  0x%p-0x%p\n", &kernel_start_bss, &kernel_end_bss);

        page_init();

        pt_init();
        slab_init();
        pframe_init();

        acpi_init();
        apic_init();
        intr_init();

        gdt_init();

        /* initialize slab allocators */
#ifdef __VM__
        anon_init();
        shadow_init();
#endif
        vmmap_init();
        proc_init();
        kthread_init();

#ifdef __DRIVERS__
        bytedev_init();
        blockdev_init();
#endif

        void *bstack = page_alloc();
        pagedir_t *bpdir = pt_get();
        KASSERT(NULL != bstack && "Ran out of memory while booting.");
        context_setup(&bootstrap_context, bootstrap, 0, NULL, bstack, PAGE_SIZE, bpdir);
        context_make_active(&bootstrap_context);

        panic("\nReturned to kmain()!!!\n");
}

/**
 * This function is called from kmain, however it is not running in a
 * thread context yet. It should create the idle process which will
 * start executing idleproc_run() in a real thread context.  To start
 * executing in the new process's context call context_make_active(),
 * passing in the appropriate context. This function should _NOT_
 * return.
 *
 * Note: Don't forget to set curproc and curthr appropriately.
 *
 * @param arg1 the first argument (unused)
 * @param arg2 the second argument (unused)
 */
static void *
bootstrap(int arg1, void *arg2)
{

        /* necessary to finalize page table information */
        pt_template_init();

        /*NOT_YET_IMPLEMENTED("PROCS: bootstrap");*/
        proc_t *proc0 = proc_create("idle_process");
        kthread_t* th = kthread_create(proc0, idleproc_run, 0, NULL);  /*???*/

        /*set curProc and curthr*/
        curproc = proc0;
        curthr = th;

 
        KASSERT(NULL != curproc); /* make sure that the "idle" process has been created successfully */
        KASSERT(PID_IDLE == curproc->p_pid); /* make sure that what has been created is the "idle" process */
        KASSERT(NULL != curthr); /* make sure that the thread for the "idle" process has been created successfully 
    

        /*run the thread inside proc0*/
        context_make_active(&th->kt_ctx);

        panic("weenix returned to bootstrap()!!! BAD!!!\n");
        return NULL;
}

static void *
test_run(int arg1, void *arg2){
    dbg_print("this is from %d\n",curproc->p_pid);
    return NULL;
}

/**
 * Once we're inside of idleproc_run(), we are executing in the context of the
 * first process-- a real context, so we can finally begin running
 * meaningful code.
 *
 * This is the body of process 0. It should initialize all that we didn't
 * already initialize in kmain(), launch the init process (initproc_run),
 * wait for the init process to exit, then halt the machine.
 *
 * @param arg1 the first argument (unused)
 * @param arg2 the second argument (unused)
 */
static void *
idleproc_run(int arg1, void *arg2)
{
   
        int status;
        pid_t child;

        /* create init proc */
        kthread_t *initthr = initproc_create();

        init_call_all();
        GDB_CALL_HOOK(initialized);

        /* Create other kernel threads (in order) */

#ifdef __VFS__
        /* Once you have VFS remember to set the current working directory
         * of the idle and init processes */


      /*  dbg_print("vfs_root_vn : %d\n", vfs_root_vn);
*/
        curproc->p_cwd = vfs_root_vn;
        vref(vfs_root_vn);
        initthr->kt_proc->p_cwd = vfs_root_vn;
        vref(vfs_root_vn);
       /* initthr->kt_proc->p_cwd = vfs_root_vn;*/

        

        do_mkdir("/dev");

        /* You can't do this until you have VFS, check the include/drivers/dev.h
         * file for macros with the device ID's you will need to pass to mknod */
         do_mknod("/dev/null", S_IFCHR, MEM_NULL_DEVID);
        do_mknod("/dev/zero", S_IFCHR, MEM_ZERO_DEVID);
        do_mknod("/dev/tty1", S_IFCHR, MKDEVID(2,1));
        do_mknod("/dev/tty0", S_IFCHR, MKDEVID(2,0));
        
       /* do_mknod("/dev/tty1", S_IFCHR, MKDEVID(2,1));*/

#endif

        /* Finally, enable interrupts (we want to make sure interrupts
         * are enabled AFTER all drivers are initialized) */
        intr_enable();
        /* Run initproc */


        sched_make_runnable(initthr);
        /* Now wait for it */
        child = do_waitpid(-1, 0, &status);
        KASSERT(PID_INIT == child);



#ifdef __MTP__
        kthread_reapd_shutdown();
#endif


#ifdef __VFS__
        /* Shutdown the vfs: */
        dbg_print("weenix: vfs shutdown...\n");
        vput(curproc->p_cwd);
        if (vfs_shutdown())
        panic("vfs shutdown FAILED!!\n");

#endif

        /* Shutdown the pframe system */
#ifdef __S5FS__
        pframe_shutdown();
#endif

        dbg_print("\nweenix: halted cleanly!\n");
        GDB_CALL_HOOK(shutdown);
        hard_shutdown();
        return NULL;
}

/**
 * This function, called by the idle process (within 'idleproc_run'), creates the
 * process commonly refered to as the "init" process, which should have PID 1.
 *
 * The init process should contain a thread which begins execution in
 * initproc_run().
 *
 * @return a pointer to a newly created thread which will execute
 * initproc_run when it begins executing
 */
static kthread_t *
initproc_create(void)
{
        proc_t *proc1 = proc_create("init_process");
        kthread_t* th = kthread_create(proc1, initproc_run, 0, NULL);  /*???*/  
        KASSERT(NULL != proc1);
        KASSERT(PID_INIT == proc1->p_pid);
        KASSERT(th != NULL);
        return th;
}

/**
 * The init thread's function changes depending on how far along your Weenix is
 * developed. Before VM/FI, you'll probably just want to have this run whatever
 * tests you've written (possibly in a new process). After VM/FI, you'll just
 * exec "/bin/init".
 *
 * Both arguments are unused.
 *
 * @param arg1 the first argument (unused)
 * @param arg2 the second argument (unused)
 */


kmutex_t mutex1, mutex2;


static void *
lockfunc1()
{
    dbg_print("\nDEADLOCK TEST\n");
    kmutex_lock(&mutex1);
    dbg_print("\n func1 succeed to lock mutex1, now switch to another thread\n");
    sched_make_runnable(curthr);
    sched_switch();

    while(curthr != mutex2.km_holder)
    {
         dbg_print("\n func1 is trying to get mutex2\n");
        kmutex_lock(&mutex2);
    }
        

    dbg_print("\nERROR: this function should \nnever return\n");
    return NULL;
} 

static void *
lockfunc2()
{
  
    kmutex_lock(&mutex2);
    dbg_print("\nfunc2 succeed to lock mutex2, now switch to another thread\n");

    while(curthr != mutex1.km_holder)
    {
         dbg_print("\n func2 is trying to get mutex1\n");
        kmutex_lock(&mutex1);
    }
    dbg_print("\nERROR: this function should never return\n");
    return NULL;
} 



#ifdef VMMAP_TEST
static int vmmap_test()
{
   
    int start1 = 32840;
    int npages1 = 10;

    int start2 = 32850;
    int npages2 = 1;

    int start3 = 32851;
    int npages3 = 9;

    int start4 = 32870;
    int npages4 = 5;

    int start5 = 32875;
    int npages5 = 15;


    vmmap_t* map = vmmap_create();


    /*test vmmap_insert */
    vmarea_t* vmarea = vmarea_alloc();
    vmarea->vma_start = start2;
    vmarea->vma_end = start2 + npages2;

    vmmap_insert(map, vmarea);
    showVmMapInfo(map, "after insert 2\n");

    vmarea = vmarea_alloc();
    vmarea->vma_start = start4;
    vmarea->vma_end = start4 + npages4;

     vmmap_insert(map, vmarea);
    showVmMapInfo(map, "after insert 4\n");


      vmarea = vmarea_alloc();
    vmarea->vma_start = start3;
    vmarea->vma_end = start3 + npages3;

     vmmap_insert(map, vmarea);
    showVmMapInfo(map, "after insert 3\n");

      vmarea = vmarea_alloc();
    vmarea->vma_start = start5;
    vmarea->vma_end = start5 + npages5;

     vmmap_insert(map, vmarea);
    showVmMapInfo(map, "after insert the 5\n");

      vmarea = vmarea_alloc();
    vmarea->vma_start = start1;
    vmarea->vma_end = start1 + npages1;

     vmmap_insert(map, vmarea);
    showVmMapInfo(map, "after insert the 1\n");

    /*test vmmap_find_range */
    int re1 = vmmap_find_range(map, 5, VMMAP_DIR_HILO);
    int re2 = vmmap_find_range(map, 5, VMMAP_DIR_LOHI);
    int re3 = vmmap_find_range(map, 15, VMMAP_DIR_HILO);
    int re4 = vmmap_find_range(map, 15, VMMAP_DIR_LOHI);
    dbg_print("result from decreasing order %d and from increasing order %d\n",re1, re2 );
    dbg_print("result from decreasing order %d and from increasing order %d\n",re3, re4 );


    /*test vmmap_remove */

    /*should modify 1 and 4, remove 2, 3, no influence to 5
    vmmap_remove(map, 32843, 28);
    showVmMapInfo(map, "after remove pages\n");

    should have no influence
    vmmap_remove(map, 32839, 1);
    showVmMapInfo(map, "after remove pages\n");

    should only modify the last one
     vmmap_remove(map, 32889, 1);
    showVmMapInfo(map, "after remove pages\n");*/

    /*test vmmap_is_range_empty*/
     KASSERT(vmmap_is_range_empty(map, 32860, 10));
     KASSERT(!vmmap_is_range_empty(map, 32860, 11));


     /*test anon object*/
     int res = vmmap_map(map, NULL, start4, npages4, PROT_READ | PROT_WRITE, MAP_PRIVATE, 0,VMMAP_DIR_HILO,NULL);
     KASSERT(res == 0);
     char writebuf[] = "hello";
     vmmap_write(map, start4<<12, writebuf, 6);
     char readbuf[1024];
     vmmap_read(map, (start4<<12) + 1, readbuf, 5);
     /*should print out ello*/
     dbg_print("read from memory object: %s\n", readbuf);



    vmmap_destroy(map);
    showVmMapInfo(map, "after destroy the vmmap\n");

    


    return 0;
}
#endif

static void *
initproc_run(int arg1, void *arg2)
{

    char *argv[] = { "ls", "dev", NULL};
    char *envp[] = { NULL };
  /*  kernel_execve("/bin/ls", argv, envp);*/
   /* kernel_execve("/usr/bin/segfault", argv, envp);*/
  /*  kernel_execve("/sbin/halt", argv, envp);*/

       int err = 0, ttyid = MKDEVID(2,0);
        kshell_t *ksh = kshell_create(0);
        KASSERT(ksh && "did not create a kernel shell as expected");
       /*  Run kshell commands until user exits */
        while ((err = kshell_execute_next(ksh)) > 0);

        KASSERT(err == 0 && "kernel shell exited with an error\n");
        kshell_destroy(ksh);

        return NULL;
}




/**
 * Clears all interrupts and halts, meaning that we will never run
 * again.
 */
static void
hard_shutdown()
{
#ifdef __DRIVERS__
        vt_print_shutdown();
#endif
        __asm__ volatile("cli; hlt");
}
